package utilHttp

import (
	"crypto/tls"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"gitee.com/hilaoyu/go-basic-utils/utilProxy"
	"gitee.com/hilaoyu/go-basic-utils/utilSsl"
	"io"
	"mime/multipart"
	"net/http"
	"net/url"
	"os"
	"path/filepath"
	"strings"
	"time"
)

const PROXY_TYPE_SOCKS5 = "socks5"

type UtilHttp struct {
	timeout                 time.Duration
	baseUrl                 string
	headers                 map[string]string
	sslVerify               bool
	sslClientCertPemPath    string
	sslClientCertPemContent []byte
	sslClientCertPemKey     []byte
	lastRespStatusCode      int
	client                  *http.Client

	useProxy            string
	proxySocks5         utilProxy.UtilProxy
	proxySocks5Addr     string
	proxySocks5user     string
	proxySocks5Password string
}

func New(baseUrl string, timeout ...time.Duration) (uh *UtilHttp) {
	uh = &UtilHttp{
		timeout:                 0,
		baseUrl:                 baseUrl,
		headers:                 map[string]string{},
		sslVerify:               true,
		sslClientCertPemPath:    "",
		sslClientCertPemContent: nil,
		sslClientCertPemKey:     nil,
		client:                  nil,
	}
	if len(timeout) > 0 {
		uh.timeout = timeout[0]
	}

	return uh.buildClient()
}

func (uh *UtilHttp) UseProxySocks5(proxyAddr string, proxyUser string, proxyPassword string) *UtilHttp {
	proxyAddr = strings.TrimSpace(proxyAddr)
	uh.useProxy = PROXY_TYPE_SOCKS5
	if "" != proxyAddr {
		uh.proxySocks5Addr = proxyAddr
		uh.proxySocks5user = proxyUser
		uh.proxySocks5Password = proxyPassword

		d, err := utilProxy.NewProxySocks5(uh.proxySocks5Addr, uh.proxySocks5user, uh.proxySocks5Password)
		if nil == err {
			uh.proxySocks5 = d
		}

	}

	return uh.buildClient()
}

func (uh *UtilHttp) buildClient() *UtilHttp {
	uh.client = nil

	tlsConfig := &tls.Config{InsecureSkipVerify: !uh.sslVerify}

	if nil == uh.sslClientCertPemContent && "" != uh.sslClientCertPemPath {
		uh.sslClientCertPemKey, uh.sslClientCertPemContent = utilSsl.ParseCertPemFile(uh.sslClientCertPemPath)
	}
	if nil != uh.sslClientCertPemContent {
		tlsCertificate, err := tls.X509KeyPair(uh.sslClientCertPemContent, uh.sslClientCertPemKey)
		if nil == err {
			tlsConfig.Certificates = []tls.Certificate{tlsCertificate}
		} else {
			fmt.Println("tls.X509KeyPair err:", err)
		}
	}

	tr := &http.Transport{
		TLSClientConfig: tlsConfig,
	}

	switch uh.useProxy {
	case PROXY_TYPE_SOCKS5:
		if nil != uh.proxySocks5 {
			tr.DialContext = uh.proxySocks5.DialContext
		}
		break
	default:
		break
	}

	uh.client = &http.Client{
		Timeout:   uh.timeout,
		Transport: tr,
	}
	return uh
}

func (uh *UtilHttp) SetBaseUrl(baseUrl string) *UtilHttp {
	uh.baseUrl = baseUrl
	return uh.buildClient()
}
func (uh *UtilHttp) SetTimeout(timeout time.Duration) *UtilHttp {
	uh.timeout = timeout
	return uh.buildClient()
}
func (uh *UtilHttp) SetClientCertPemPath(path string) *UtilHttp {
	uh.sslClientCertPemPath = path
	return uh.buildClient()
}

func (uh *UtilHttp) SetClientCertPemContent(pemContent []byte, key []byte) *UtilHttp {
	uh.sslClientCertPemContent = pemContent
	uh.sslClientCertPemKey = key
	return uh.buildClient()
}

func (uh *UtilHttp) SetSslVerify(v bool) *UtilHttp {
	uh.sslVerify = v
	return uh.buildClient()
}
func (uh *UtilHttp) AddHeader(k string, v string) *UtilHttp {
	k = strings.TrimSpace(k)
	v = strings.TrimSpace(v)
	if "" != k {
		uh.headers[k] = v
	}
	return uh.buildClient()
}

func (uh *UtilHttp) BasicAuth(user string, password string) *UtilHttp {
	uh.AddHeader("Authorization", fmt.Sprintf("Basic %s", base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", user, password)))))
	return uh.buildClient()
}
func (uh *UtilHttp) BuildRemoteUrl(method string, path string, params *url.Values) (remoteUrl string, err error) {
	method = strings.ToUpper(method)
	remoteUrl = path
	if "" != uh.baseUrl {
		remoteUrl = strings.TrimRight(uh.baseUrl, "/") + "/" + strings.TrimLeft(path, "/")
	}

	if "GET" == method || "DELETE" == method {
		urlParse, err1 := url.Parse(remoteUrl)
		if nil != err1 {
			err = err1
			return
		}
		query := urlParse.Query()
		for qk, _ := range *params {
			query.Set(qk, params.Get(qk))
		}
		urlParse.RawQuery = query.Encode()

		remoteUrl = urlParse.String()
		*params = url.Values{}
	}
	return
}

func (uh *UtilHttp) GetLastRespStatusCode() int {
	return uh.lastRespStatusCode
}

func (uh *UtilHttp) Request(method string, path string, params url.Values, headers map[string]string) (resp *http.Response, err error) {
	method = strings.ToUpper(method)
	remoteUrl, err := uh.BuildRemoteUrl(method, path, &params)

	//fmt.Println(remoteUrl, params.Encode())

	req, err := http.NewRequest(method, remoteUrl, strings.NewReader(params.Encode()))

	if err != nil {
		return nil, err
	}

	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

	for hk, hv := range uh.headers {
		fmt.Println("uh.headers", hk, hv)
		req.Header.Set(hk, hv)
	}

	for hk, hv := range headers {
		req.Header.Set(hk, hv)
	}
	//godump.Dump(req)
	return uh.client.Do(req)
}

func (uh *UtilHttp) RequestPlain(method string, path string, params url.Values, headers map[string]string) (body []byte, err error) {
	resp, err := uh.Request(method, path, params, headers)
	if err != nil {
		return
	}
	uh.lastRespStatusCode = resp.StatusCode

	//godump.Dump(resp)
	defer resp.Body.Close()

	body, err = io.ReadAll(resp.Body)
	return
}

func (uh *UtilHttp) RequestJson(v interface{}, method string, path string, params url.Values, headers map[string]string) error {

	body, err := uh.RequestPlain(method, path, params, headers)
	if err != nil {
		return err
	}
	//fmt.Println(string(body))
	err = json.Unmarshal(body, &v)
	if err != nil {
		return err
	}
	return nil
}

func (uh *UtilHttp) PostFile(path string, filedName string, file string, params url.Values, headers map[string]string) (resp *http.Response, err error) {
	remoteUrl, err := uh.BuildRemoteUrl("post", path, &params)

	if nil != err {
		return
	}
	pipeReader, pipeWriter := io.Pipe()
	multipartWriter := multipart.NewWriter(pipeWriter)

	go func() {
		defer pipeWriter.Close()
		defer multipartWriter.Close()
		for pk, _ := range params {
			multipartWriter.WriteField(pk, params.Get(pk))
		}

		part, err1 := multipartWriter.CreateFormFile(filedName, filepath.Base(file))
		if err1 != nil {
			err = err1
			return
		}
		fileHandle, err1 := os.Open(file)
		if err1 != nil {
			err = err1
			return
		}
		defer fileHandle.Close()
		if _, err1 = io.Copy(part, fileHandle); nil != err1 {
			err = err1
			return
		}
	}()

	req, err := http.NewRequest("POST", remoteUrl, pipeReader)
	if err != nil {
		return
	}
	for hk, hv := range uh.headers {
		fmt.Println("uh.headers", hk, hv)
		req.Header.Set(hk, hv)
	}

	for hk, hv := range headers {
		req.Header.Set(hk, hv)
	}

	req.Header.Set("Content-Type", multipartWriter.FormDataContentType())
	resp, err = uh.client.Do(req)

	return
}
func (uh *UtilHttp) PostFilePlain(path string, filedName string, file string, params url.Values, headers map[string]string) (body []byte, err error) {

	resp, err := uh.PostFile(path, filedName, file, params, headers)
	uh.lastRespStatusCode = resp.StatusCode
	if err != nil {
		return
	}
	//godump.Dump(resp)

	defer resp.Body.Close()

	body, err = io.ReadAll(resp.Body)
	return
}

func (uh *UtilHttp) PostFileJson(v interface{}, path string, filedName string, file string, params url.Values, headers map[string]string) error {

	body, err := uh.PostFilePlain(path, filedName, file, params, headers)
	if err != nil {
		return err
	}
	//fmt.Println(string(body))
	err = json.Unmarshal(body, &v)
	if err != nil {
		return err
	}
	return nil
}

func Request(method string, remoteUrl string, params url.Values, headers map[string]string, timeout ...time.Duration) (resp *http.Response, err error) {

	uh := New("", timeout...)
	return uh.Request(method, remoteUrl, params, headers)
}

func RequestJson(v interface{}, method string, remoteUrl string, params url.Values, headers map[string]string, timeout ...time.Duration) error {
	uh := New("", timeout...)
	return uh.RequestJson(v, method, remoteUrl, params, headers)
}
